﻿using CliWrap;
using CliWrap.Buffered;
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;

namespace Away.NetMap.Nmap;

public class NmapParams
{
    /// <summary>
    /// 开始IP
    /// </summary>
    public string Start { get; set; } = null!;
    /// <summary>
    /// 结束IP
    /// </summary>
    public string End { get; set; } = null!;
    /// <summary>
    /// 线程数
    /// </summary>
    public int Threads { get; set; } = 100;
    /// <summary>
    /// 超时时间
    /// </summary>
    public int Timeout { get; set; } = 300;
    /// <summary>
    /// 是否扫描私有地址 0:否
    /// </summary>
    public int IsScanPrivateIP { get; set; }
}

[ServiceInject(ServiceLifetime.Scoped, true)]
public class NmapService
{
    private readonly IServiceScope _scope;
    public NmapService(IServiceProvider serviceProvider)
    {
        _scope = serviceProvider.CreateScope();
        ScanCompleted += Completed;
    }

    private IServiceProvider ServiceProvider => _scope.ServiceProvider;
    private PortRepository Rep => ServiceProvider.GetRequiredService<PortRepository>();
    private ILogger<NmapService> Logger => ServiceProvider.GetRequiredService<ILogger<NmapService>>();

    private NmapParams? _params;
    public void Run(NmapParams p, CancellationToken token)
    {
        System.Diagnostics.Stopwatch stopwatch = new();
        stopwatch.Start();

        _params = p;
        var start = IPHelper.ToInt(p.Start);
        var end = IPHelper.ToInt(p.End);

        var total = Math.Abs(end - start) + 1;
        Logger.LogInformation("扫描IP区间：[{},{}] 共{}个", p.Start, p.End, total);
        var threads = p.Threads;
        var ip = start;
        while (true)
        {
            if (token.IsCancellationRequested)
            {
                break;
            }
            var num = threads;
            if (ip > end)
            {
                break;
            }
            var caps = end - ip;
            if (caps > 0 && caps < threads)
            {
                num = caps + 1;
            }

            Enumerable.Range(ip, num)
            .Select(IPHelper.ToString)
            .AsParallel()
            .OrderByDescending(o => o)
            .WithDegreeOfParallelism(threads)
            .WithCancellation(token)
            .ForAll(async o => await ScanOne(o));

            ip += threads;
        }
        Logger.LogInformation("扫描IP区间：[{},{}] 共{}个 耗时：{}", p.Start, p.End, total, stopwatch.Elapsed);
    }

    public async Task ScanOne(string ip)
    {
        // 跳过私有地址
        if (_params!.IsScanPrivateIP != 0 && IPBlackList.IsPrivateIP(IPHelper.ToInt(ip)))
        {
            return;
        }

        var res = await Cli.Wrap("nmap").WithArguments(ip).ExecuteBufferedAsync();
        var text = res.StandardOutput.ToString();
        var list = new List<NmapResult>();
        using var sr = new StringReader(text);
        var line = string.Empty;
        while ((line = sr.ReadLine()) != null)
        {
            var pattern = @"^(?<port>\d+)/(?<proto>tcp|udp)\s+(?<state>\w+)\s+(?<service>\w+)";
            var reg = Regex.Match(line, pattern);
            if (!reg.Success)
            {
                continue;
            }

            var port = reg.Result("${port}");
            var proto = reg.Result("${proto}");
            var state = reg.Result("${state}");
            var service = reg.Result("${service}");
            list.Add(new NmapResult
            {
                Port = Convert.ToInt32(port),
                Protocol = proto,
                State = state,
                Service = service,
            });
        }
        ScanCompleted?.Invoke(new NmapCompletedEventArgs
        {
            IP = ip,
            Items = list
        });
    }

    private delegate void NmapCompletedEventHandler(NmapCompletedEventArgs args);
    private event NmapCompletedEventHandler ScanCompleted;
    private static readonly object _lock = new();
    private void Completed(NmapCompletedEventArgs args)
    {
        lock (_lock)
        {
            foreach (var res in args.Items)
            {
                Logger.LogTrace("{}://{}:{}", res.Service, args.IP, res.Port);
                Rep.Update(args.IP, res.Port, res.Service);
            }
        }
    }

}

public class NmapCompletedEventArgs : EventArgs
{
    public required string IP { get; set; }
    public required List<NmapResult> Items { get; set; }
}

public class NmapResult
{
    public int Port { get; set; }
    public required string Protocol { get; set; }
    public required string State { get; set; }
    public required string Service { get; set; }
}


